/*
 * File Name：HttpUpload.java
 * Copyright：Copyright 2008-2015 CiWong.Inc. All Rights Reserved.
 * Description： HttpUpload.java
 * Modify By：Administrator
 * Modify Date：2015年4月15日
 * Modify Type：Add
 */
package com.ciwong.libs.utils.volley;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import org.apache.commons.httpclient.HttpStatus;
import org.json.JSONException;
import org.json.JSONObject;

import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;

import com.ciwong.libs.utils.volley.BaseRequest.ResponseCallback;
import com.google.gson.Gson;

/**
 * HTTP大文件上传
 * 
 * @author Administrator
 * @version ciwong v.1.0 2015年4月15日
 * @since ciwong v.1.0
 */
public class HttpUpload
{
    /**
     * 请求无错误
     */
    public static final int REQUEST_ERROR_NONE = 0;

    /**
     * 请求错误：输入的请求地址有误
     */
    public static final int REQUEST_ERROR_URL = 1;

    /**
     * 请求错误：执行请求发生异常
     */
    public static final int REQUEST_ERROR_EXCEPTION = 2;

    /**
     * 请求错误：网络未连接
     */
    public static final int REQUEST_ERROR_NET_UNCONNECT = 3;

    /**
     * 请求错误：请求超时
     */
    public static final int REQUEST_ERROR_TIME_OUT = 4;

    /**
     * 请求错误：传入的文件有误
     */
    public static final int REQUEST_ERROR_FILE = 5;

    /**
     * 请求错误：验证信息有误
     */
    public static final int REQUEST_ERROR_VERIFY = 6;

    /**
     * 请求错误：证书无效或没有
     */
    public static final int REQUEST_ERROR_SSL = 8;

    /**
     * 数据错误，请求成功，返回的数据不对
     */
    public static final int REQUEST_ERROR_DATA = 11;

    /**
     * 请求错误：请求的地址找不到
     */
    public static final int REQUEST_ERROR_404 = 404;

    /**
     * 请求错误：服务器内部出错
     */
    public static final int REQUEST_ERROR_500 = 500;

    /**
     * 上传完毕
     */
    private static final int HANDLER_FLAG_COMPLETE_UPLOAD = 1;

    /**
     * 出现异常
     */
    private static final int HANDLER_FLAG_ERROR_UPLOAD = 2;

    /**
     * 重试
     */
    private static final int HANDLER_FLAG_RETRIES = 3;

    /**
     * 两横线
     */
    private final String TWO_HYPHEN = "--";

    /**
     * 分隔符
     */
    private String BOUNDARY = UUID.randomUUID().toString();

    /**
     * 换行符
     */
    private final String NEW_LINE_STR = "\r\n";

    /**
     * 最大重连次数
     */
    public static final int HTTP_MAX_RETRIES = 3;

    /**
     * 响应结果类型：字符串
     */
    public static final int RESPONSE_DATA_TYPE_STRING = 1;

    /**
     * 响应结果类型：指定对象
     */
    public static final int RESPONSE_DATA_TYPE_OBJECT = 2;

    /**
     * 响应数据字段：ret
     */
    public static final String RESPONSE_DATA_COLUMN_RET = "ret";

    /**
     * 响应数据字段：errcode
     */
    public static final String RESPONSE_DATA_COLUMN_ERRCODE = "errcode";

    /**
     * 响应数据字段：msg
     */
    public static final String RESPONSE_DATA_COLUMN_MSG = "msg";

    /**
     * 响应数据字段：data
     */
    public static final String RESPONSE_DATA_COLUMN_DATA = "data";

    private Map<String, String> params;

    private String[] paths;

    private String url;

    private ResponseCallback callback;

    private HttpURLConnection connection;

    /**
     * 上传线程
     */
    private Thread uploadThread;

    /**
     * 是否已取消
     */
    private boolean mCanceled;

    /**
     * 输出流
     */
    private DataOutputStream dos;

    /**
     * 响应结果类型
     */
    private int responseDataType = RESPONSE_DATA_TYPE_STRING;

    private int resultCode;

    private int errorCode;

    private String msg;

    /**
     * 响应结果:对象
     */
    private Class<?> responseClazz;

    /**
     * 响应结果:对象数组
     */
    private Type responseType;

    /**
     * 重试次数
     */
    private int retries = 0;

    /**
     * 错误码
     */
    private int errorType = REQUEST_ERROR_NONE;

    private Handler mHandler = new Handler()
    {

        @Override
        public void handleMessage(Message msg)
        {
            if (mCanceled)
            {
                return;
            }
            switch (msg.what)
            {
                case HANDLER_FLAG_COMPLETE_UPLOAD:
                    if (responseDataType == RESPONSE_DATA_TYPE_OBJECT)
                    {
                        callback.success(msg.obj, resultCode, errorCode,
                                HttpUpload.this.msg);
                    }
                    else
                    {
                        callback.success(String.valueOf(msg.obj));
                    }
                    break;

                case HANDLER_FLAG_ERROR_UPLOAD:
                    handlerException((Exception) msg.obj);
                    break;

                case HANDLER_FLAG_RETRIES:
                    executeUpload();
                    break;

                default:
                    break;
            }
        }
    };

    /**
     * 
     * 构造函数
     * 
     * @param params
     *            参数
     * @param paths
     *            文件路径
     * @param url
     *            上传地址
     * @param callback
     *            回调
     */
    public HttpUpload(Map<String, String> params, String[] paths, String url,
            ResponseCallback callback)
    {
        this.params = params;
        this.paths = paths;
        this.url = url;
        this.callback = callback;
    }

    /**
     * 
     * 执行上传
     * 
     */
    public void executeUpload()
    {
        uploadThread = new Thread(new Runnable()
        {

            @Override
            public void run()
            {
                try
                {
                    initURLConnection();
                    dos.write(getWriteParams());
                    addBodyIfExists();
                    completeUpload();
                }
                catch (Exception e)
                {
                    if (e instanceof SocketTimeoutException
                            && retries < HTTP_MAX_RETRIES)
                    {
                        retries++;
                        mHandler.obtainMessage(HANDLER_FLAG_RETRIES)
                                .sendToTarget();
                    }
                    else
                    {
                        e.printStackTrace();
                        mHandler.obtainMessage(HANDLER_FLAG_ERROR_UPLOAD, e)
                                .sendToTarget();
                    }
                }
            }
        });
        uploadThread.start();
    }

    /**
     * 
     * 初始化URLConnection
     * 
     * @throws IOException
     */
    private void initURLConnection() throws IOException
    {
        URL parsedUrl = new URL(url);
        connection = (HttpURLConnection) parsedUrl.openConnection();
        connection.setUseCaches(false);
        connection.setDoInput(true);
        connection.setDoOutput(true);
        connection.setRequestMethod("POST");
        connection.setRequestProperty("Accept-Encoding", "gzip, deflate");
        if (Build.VERSION.SDK != null && Build.VERSION.SDK_INT > 13)
        {
            connection.setRequestProperty("Connection", "close");
        }
        else
        {
            connection.setRequestProperty("Connection", "Keep-Alive");
        }
        connection.setFixedLengthStreamingMode(getTotalSizeOfBody());
        connection.setRequestProperty("Content-Type",
                "multipart/form-data; boundary=" + BOUNDARY);
        dos = new DataOutputStream(connection.getOutputStream());
    }

    /**
     * 
     * 获取请求头的总大小
     * 
     * @return 包体总大小
     * @throws IOException
     */
    private int getTotalSizeOfBody() throws IOException
    {
        int totalSize = 0;
        totalSize += getWriteParams().length;
        totalSize += getBodySize();
        return totalSize;
    }

    /**
     * 
     * 获取写入参数
     * 
     * @return 写入参数
     */
    private byte[] getWriteParams()
    {
        String paramStr = "";
        if (params != null)
        {
            Set<String> keys = params.keySet();
            for (String str : keys)
            {
                String value = params.get(str);
                paramStr += getParamStr(str, value);
            }
        }
        return paramStr.getBytes();
    }

    /**
     * 拼接参数串
     * 
     * @param key
     *            键
     * @param value
     *            值
     * @return 参数字符串
     */
    private String getParamStr(String key, String value)
    {
        StringBuilder sb = new StringBuilder();
        sb.append(TWO_HYPHEN + BOUNDARY + NEW_LINE_STR);
        sb.append("Content-Disposition: form-data; name=\"");
        sb.append(key);
        sb.append("\"");
        sb.append(NEW_LINE_STR);
        sb.append(NEW_LINE_STR);
        sb.append(value);
        sb.append(NEW_LINE_STR);
        return sb.toString();
    }

    private int getBodySize() throws IOException
    {
        int size = 0;
        int index = 0;
        for (String path : paths)
        {
            File file = new File(path);
            FileInputStream fis = new FileInputStream(file);
            size += getFirstBoundary().length;
            size += getContentDispositionBytes("file" + ++index, file.getName()).length;
            size += fis.available();
            size += NEW_LINE_STR.getBytes().length;
            fis.close();
        }
        size += writeEndBoundary().length;
        return size;
    }

    /**
     * 
     * 添加文件上传Body
     * 
     * @throws IOException
     */
    private void addBodyIfExists()
    {
        int index = 0;
        for (String path : paths)
        {
            File file = new File(path);
            FileInputStream fis = null;
            try
            {
                fis = new FileInputStream(file);
                dos.write(getFirstBoundary());
                dos.write(getContentDispositionBytes("file" + ++index,
                        file.getName()));
                int maxBufferSize = 1 * 1024;
                int available = fis.available();
                int bufferSize = Math.min(available, maxBufferSize);
                byte[] buffer = new byte[bufferSize];
                int len = 0;
                while (!mCanceled
                        && (len = fis.read(buffer, 0, bufferSize)) != -1)
                {
                    dos.write(buffer, 0, len);
                    System.out.println(dos.size());
                }
                dos.writeBytes(NEW_LINE_STR);
            }
            catch (FileNotFoundException e)
            {
                e.printStackTrace();
                mHandler.obtainMessage(HANDLER_FLAG_ERROR_UPLOAD, e)
                        .sendToTarget();
            }
            catch (IOException e)
            {
                e.printStackTrace();
                mHandler.obtainMessage(HANDLER_FLAG_ERROR_UPLOAD, e)
                        .sendToTarget();
            }
            finally
            {
                try
                {
                    fis.close();
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                    mHandler.obtainMessage(HANDLER_FLAG_ERROR_UPLOAD, e)
                            .sendToTarget();
                }
            }
        }
        try
        {
            dos.write(writeEndBoundary());
        }
        catch (IOException e)
        {
            e.printStackTrace();
            mHandler.obtainMessage(HANDLER_FLAG_ERROR_UPLOAD, e).sendToTarget();
        }
    }

    /**
     * 
     * 获取开始分隔符
     * 
     * @return 开始分隔符字符串
     */
    private byte[] getFirstBoundary()
    {
        return (TWO_HYPHEN + BOUNDARY + NEW_LINE_STR).getBytes();
    }

    /**
     * 
     * 获取内容描述
     * 
     * @param key
     *            键
     * @param fileName
     *            文件名
     */
    private byte[] getContentDispositionBytes(String key, String fileName)
    {
        StringBuilder sb = new StringBuilder();
        sb.append("Content-Disposition: form-data; name=\"" + key + "\"");
        // 文本参数没有filename参数,设置为空即可
        if (!TextUtils.isEmpty(fileName))
        {
            sb.append("; filename=\"" + fileName + "\"");
        }
        sb.append(NEW_LINE_STR);
        sb.append("Content-Type: text/plain" + NEW_LINE_STR + NEW_LINE_STR);
        return sb.toString().getBytes();
    }

    /**
     * 
     * 写入结束分隔符
     * 
     */
    private byte[] writeEndBoundary()
    {
        return (TWO_HYPHEN + BOUNDARY + TWO_HYPHEN + NEW_LINE_STR).getBytes();
    }

    /**
     * 
     * 完成上传
     * 
     * @throws Exception
     */
    private void completeUpload() throws Exception
    {
        dos.flush();
        dos.close();
        if (!mCanceled)
        {
            getResponse();
        }
    }

    /**
     * 
     * 获取请求响应
     * 
     * @throws Exception
     */
    private void getResponse() throws Exception
    {
        int responseCode = connection.getResponseCode();
        String responseMessage = connection.getResponseMessage();
        switch (responseCode)
        {
            case HttpStatus.SC_OK:
                writeResponse(connection.getInputStream());
                break;

            case HttpStatus.SC_INTERNAL_SERVER_ERROR:
            case HttpStatus.SC_NOT_FOUND:
                throw new UploadException(responseCode);

            default:
                throw new Exception(responseMessage);
        }
    }

    /**
     * 
     * 写入请求响应
     * 
     * @param in
     *            输出流
     * @throws IOException
     */
    private void writeResponse(InputStream in) throws IOException
    {
        InputStreamReader isr = new InputStreamReader(in);
        BufferedReader br = new BufferedReader(isr);
        StringBuffer sb = new StringBuffer();
        String readLine = null;
        while ((readLine = br.readLine()) != null)
        {
            sb.append(readLine);
        }
        in.close();
        connection.disconnect();
        Object obj = sb.toString();
        if (responseDataType == RESPONSE_DATA_TYPE_OBJECT)
        {
            if (responseClazz != null)
            {
                try
                {
                    JSONObject jsonObject = new JSONObject(obj.toString());
                    resultCode = jsonObject.getInt(RESPONSE_DATA_COLUMN_RET);
                    errorCode = jsonObject.getInt(RESPONSE_DATA_COLUMN_ERRCODE);
                    msg = jsonObject.getString(RESPONSE_DATA_COLUMN_MSG);
                    if (jsonObject.has(RESPONSE_DATA_COLUMN_DATA))
                    {
                        String data = jsonObject
                                .getString(RESPONSE_DATA_COLUMN_DATA);
                        if (data != null && !"".equals(data))
                        {
                            obj = new Gson().fromJson(data, responseClazz);
                        }
                    }
                }
                catch (JSONException e)
                {
                    e.printStackTrace();
                }
            }
        }
        mHandler.obtainMessage(HANDLER_FLAG_COMPLETE_UPLOAD, obj)
                .sendToTarget();
    }

    /**
     * 处理异常
     * 
     * @param e
     *            异常
     */
    private void handlerException(Exception e)
    {
        if (e instanceof SocketException || e instanceof MalformedURLException)
        {
            this.errorType = REQUEST_ERROR_URL;
        }
        else if (e instanceof java.net.ConnectException
                || e instanceof java.net.SocketTimeoutException
                || e instanceof EOFException)
        {
            this.errorType = REQUEST_ERROR_TIME_OUT;
        }
        else if (e instanceof javax.net.ssl.SSLHandshakeException)
        {
            this.errorType = REQUEST_ERROR_SSL;
        }
        else if (e instanceof IllegalStateException)
        {
            this.errorType = REQUEST_ERROR_URL;
        }
        else if (e instanceof FileNotFoundException)
        {
            this.errorType = REQUEST_ERROR_FILE;
        }
        else if (e instanceof UploadException)
        {
            this.errorType = REQUEST_ERROR_500;
        }
        else
        {
            this.errorType = REQUEST_ERROR_EXCEPTION;
        }
        callback.error(errorType, e.getMessage());
    }

    /**
     * 文件上传异常
     * 
     * @author Administrator
     * 
     */
    public static class UploadException extends Exception
    {
        /**
         * 异常码
         */
        private int exceptionCode;

        public UploadException(int exceptionCode)
        {
            super();
            this.exceptionCode = exceptionCode;
        }

        public int getExceptionCode()
        {
            return exceptionCode;
        }

        public void setExceptionCode(int exceptionCode)
        {
            this.exceptionCode = exceptionCode;
        }

    }

    /**
     * 设置调用者所需的数据的类型<br />
     * 
     * @param responseDataType
     *            数据类型<br />
     *            {@link HttpRequest#RESPONSE_DATA_TYPE_STRING}
     *            {@link HttpRequest#RESPONSE_DATA_TYPE_OBJECT}
     *            {@link HttpRequest#RESPONSE_DATA_TYPE_ARRAY}
     */
    public void setResponseDataType(int responseDataType)
    {
        this.responseDataType = responseDataType;
    }

    /**
     * 在执行请求完后，若需要将数据转换成一个类对象，则必须要设置对象的Class .
     * 
     * @param clazz
     *            对象class
     */
    public void setResponseClazz(Class<?> responseClazz)
    {
        this.responseClazz = responseClazz;
    }

    /**
     * 
     * 在执行请求完后，若需要将数据转换成一个类对象，则必须要设置对象的Type<br />
     * 方法: Type t = new TypeToken<自己类的名称>(){}.getType();
     * 
     * @param responseType
     *            responseType
     */
    public void setResponseType(Type responseType)
    {
        this.responseType = responseType;
    }

    public void cancelUpload()
    {
        if (uploadThread != null && uploadThread.isAlive())
        {
            mCanceled = true;
        }
    }
}
